package org.groovyflow.util


class PatternMatchingTest extends GroovyTestCase {
  @Typed
  public void testPredicateReturningScalarValueDoesNotNeedToPutItInAList() {
    def expected = "hello"
    assert expected == new PatternMatch().match(
            new Case({expected}, {it})
    )
  }
  
  public void testTheUseOfSimpleCaseObjectsThatDoNotDependOnClosureScopedVariable() {
    String messagePassedFromPredictateToAction = "message"
    def argsToPassToPredicatesAndActions = [1, 2]
    //We don't need to encase the action's return in a list when the return is a single scalsr value, but
    // let's check that things work ok when we choose to do that.
    def closureUsedByPredicateThatServesToSendAdditionalArgToPredicate = {[messagePassedFromPredictateToAction]}
    assert 2 + messagePassedFromPredictateToAction ==
            simpleCaseObjectsThatDoNotDependOnClosureScopedVariablesImpl(argsToPassToPredicatesAndActions,
                    closureUsedByPredicateThatServesToSendAdditionalArgToPredicate)
  }

  public void testTheUseOfSimpleCaseObjectsThatDoNotDependOnClosureScopedVariablesAndPredicatedDoesNotBotherWithListReturn() {
    def argsToPassToPredicatesAndActions = [1, 2]
    String messagePassedFromPredictateToAction = "message"

    //This time we don't bother with sticking the returned element ijn a list, knowing that PatternMatcher will handle that.
    def closureUsedByPredicateThatServesToSendAdditionalArgToPredicate = {messagePassedFromPredictateToAction}
    assert 2 + messagePassedFromPredictateToAction ==
            simpleCaseObjectsThatDoNotDependOnClosureScopedVariablesImpl(argsToPassToPredicatesAndActions,
                    closureUsedByPredicateThatServesToSendAdditionalArgToPredicate)
  }


  def simpleCaseObjectsThatDoNotDependOnClosureScopedVariablesImpl(def args, def casePredMessageReturner) {
    //Note that casePredMessageReturner returns a value that is available to the action after all the
    //'args' values have been declared.  This value is named 'messageFromPred' below.
    new PatternMatch().args(args).match(
            new Case(
                    //Returning a scalar value, not a list, is ok here.
                    {x, y ->  x > 0 || y > 0 ? casePredMessageReturner() : false},
                    {x, y, String messageFromPred -> x * y + messageFromPred}
            )
    )
  }

  public void testTheUseOfArgForCasesThatDoNotDependOnClosureScopeWhenTheArgIsAScalar() {
    String messagePassedFromPredictateToAction = "message"
    //Another way of calling ths args method:
    //passing a different arg to predicate than to action, and not putting predicate or
    //action args into a list, since they're both scalar values.
    assert 2 + messagePassedFromPredictateToAction == new PatternMatch().args(1, 2).match(
            new Case(
                    {x ->  x > 0 ? messagePassedFromPredictateToAction : false},
                    { y, message ->  y + message}
            )
    )
  }

  public void testTheUseOfArgForCasesThatDoNotDependOnClosureScopeInputArgIsScalarAndArgsPassedByPredicateAreNotScalar(){
        assert "abc" == new PatternMatch().args("a").match(
            new Case({["b","c"]}, {a,b,c -> a + b + c})
    )
  }

  public void testTheUseOfArgForCasesThatDoNotDependOnClosureScopeWhenBothInputAndArgsPassedByPredicateAreNotScalar(){
    assert "abcd" == new PatternMatch().args(["a", "b"]).match(
            new Case({["c","d"]}, {a,b,c,d -> a + b + c +d})
    )

  }

  public void testSettingCasesRatherThanPassingThemInAsArguments() {
    Var var = new Var("x")
    def match = new PatternMatch()
    match.cases << new Case({var.name == "x"}, {5})
    assert 5 == match.match()
  }


  @Typed
  public void testAbiltyToPreventExceptionWhenNoMatch() {
    def match = new PatternMatch()
    try {
      match.match()
    } catch (NoMatchException es) {}
    match.okToThrowExceptionWhenNoMatch(false)
    match.match()
  }

  @Typed
  public void testPredicatePassesAListRatherThanOneOrMoreScalarsToAction() {
    //An action needs to wrap a list inside a list in order to pass it to an action
    def expected = [1, 2, 3]
    assert expected == new PatternMatch().match(
            //right here
            new Case({ [expected] }, {it})
    )
  }

  public void testPredicatePassesAListToActionAfterHavingCalledTheArgsMethod() {
    def originalList = [1, 2, 3]
    def argsForBothPredAndAction = ["hey", "ho"]
    assert (originalList + argsForBothPredAndAction) == new PatternMatch().args(argsForBothPredAndAction).match(
            //Need to put list into list
            new Case({ hey, ho -> def temp = originalList.clone(); temp << hey; temp << ho; [temp]},
                    {a, b, list -> list})
    )
  }

  @Typed
  public void testChainingWithGroovypp(){
    assert 2 == new PatternMatch().args(1).match(new Case({ ((int) it) + 1},{ arg, passed -> passed}))
  }

  public void testTreeVar() {
    assert 5 == treeEvalRecursive(new Var("x"), makeEnv())
  }

  public void testTreeConstant() {
    def c = makeConstant()
    assert c.v == treeEvalRecursive(c, makeEnv())
  }

  public void testTreeSumOfConstants() {
    def constant = makeConstant()
    def sum = new Sum(constant, constant)
    assert 2 * constant.v == treeEvalRecursive(sum, makeEnv())
  }

  //As of this writing, p. 11 of http://www.scala-lang.org/docu/files/ScalaTutorial.pdf has an example that
  //this test emulates
  public void testTreeScalaTutorialExample() {
    def expression = new Sum(new Sum(new Var("x"), new Var("x")), new Sum(new Constant(7), new Var("y")))
    assert 24 == treeEvalRecursive(expression, makeEnv())
  }

  public void testTreeScalaTutorialExampleWhereCasesDoNotUseClosureScopedVariables() {
    def expression = new Sum(new Sum(new Var("x"), new Var("x")), new Sum(new Constant(7), new Var("y")))
    assert 24 == treeEvalRecursiveWhereCasesDoNotUseClosureScopedVariables(expression, makeEnv())
  }


  def treeEvalRecursive(tree, env) {
    new PatternMatch().match(
            new Case({tree instanceof Sum ? [tree.left, tree.right] : false }, { left, right -> treeEvalRecursive(left, env) + treeEvalRecursive(right, env) }),
            new Case({tree instanceof Var }, { env(tree) }),
            new Case({tree instanceof Constant }, {tree.v})
    )
  }

  def treeEvalRecursiveWhereCasesDoNotUseClosureScopedVariables(def treeNotDirect, def envNotDirect) {
    //Note that 1) cases do not refer to method parameters, and 2) cases need only declare the parameters
    //they require (e.g. some cases only require a 'tree' parameter, some require  more parameters.
    //Note especially the action in the first case below.  It accepts the arguments passed into the args method,
    //and also accepts the two arguments created by that action's predicate.  Further, note that the predicate had
    //to put those two arguments into a list, but PatternMatch took them out of the list and applied them as
    //individual arguments to the action.  (If the predicate had only wished to return one value, it
    //wouldn't have had to put it in a list-- PatternMatch would have figured out the right thing to do in that case.
    new PatternMatch().args([treeNotDirect, envNotDirect]).match(
            new Case({tree, env -> tree instanceof Sum ? [tree.left, tree.right] : false }, { tree, env, left, right -> treeEvalRecursiveWhereCasesDoNotUseClosureScopedVariables(left, env) + treeEvalRecursiveWhereCasesDoNotUseClosureScopedVariables(right, env)}),
            new Case({tree -> tree instanceof Var }, { tree, env -> env(tree) }),
            new Case({tree -> tree instanceof Constant }, {tree -> tree.v})
    )
  }

  def makeEnv() {
    //Create the environment with pattern matching instead of a map, just to show the scope of the construct
    {  Var var ->
      new PatternMatch().match(
              new Case({var.name == "x"}, {5}),
              new Case({var.name == "y"}, {7})
      )
    }
  }

  def makeConstant(){
    new Constant(3)
  }
}


